﻿using UnityEditor;
using UnityEngine;


namespace XYRendering.Vegetation
{
    
    [ExecuteAlways]
    public class VGInstancingDataRenderer : MonoBehaviour
    {
        public VGInstancingData bakeData;
        
        [Range(1,3)]
        public int lodCount = 3;
        
        public Vector4 lodDistances = new Vector4(5, 50, 200, 1000);

        [Range(-10, 10)]
        public float heightOffset = 0;


#if UNITY_EDITOR
        public int maxLodDistance = 500;

        public bool showLodBands = false;
        
        public Color lodBandsColor = Color.white;

        public bool showBounds = false;
        
        public Color boundsColor = Color.white;
        
        public Color lableColor = Color.yellow;
#endif
        
        VGInstancingData _RuntimeData;
        
        CullingGroup _CullingGroup;

        private bool _isInited = false;

        private void OnEnable()
        {
            Init();
            
            Debug.Log($"当前加载数据  {this.name} + : { lodDistances}");
        }


        private void OnDisable()
        {
            DeInit();
        }

        private void OnDestroy()
        {
            // DeInit();
        }


        private void Update()
        {
            if (!_isInited)
                Refresh();
        }


#if UNITY_EDITOR
        private void OnDrawGizmos()
        {
            if (!_isInited)
                    return;
            
            var lableStyle = new GUIStyle();
            lableStyle.normal.textColor = lableColor;
            lableStyle.fontSize = 18;
            if (showBounds && _RuntimeData.blocksCount > 0)
            {
                Color oldColor = Gizmos.color;
                Gizmos.color = boundsColor;
                for (int i = 0; i < _RuntimeData.blocksCount; i++)
                {
                    var b = _RuntimeData.blocks[i].bounds;
                    Gizmos.DrawWireSphere(b.center + new Vector3(0, heightOffset, 0), Mathf.Min(Mathf.Min(b.extents.x, b.extents.y), b.extents.z));
                    Handles.Label(b.center, i.ToString(), lableStyle);
                }

                Gizmos.color = oldColor;
            }

            if (showLodBands)
            {
                Ray ray = new Ray();
                ray.origin = GetCullingCamera().transform.position;
                ray.direction = Vector3.down;

                if (Physics.Raycast(ray, out var hitInfo, 50,1 << gameObject.layer))
                {
                    Color oldColor = Handles.color;
                    Handles.color = lodBandsColor;
                    for (int i = 0; i < lodCount; i++)
                    {
                        Handles.DrawWireDisc(hitInfo.point, Vector3.up, lodDistances[i]);    
                    }
                    Handles.color = oldColor;
                }
            }
        }
#endif


        void Init()
        {
            if (_isInited)
                return;
            
            if (bakeData == null)
                return;

            if (bakeData.blocksCount <= 0 || bakeData.instancesCount <= 0)
                return;

            Camera camera = GetCullingCamera();
            if (camera == null)
                return;
            
            _RuntimeData = ScriptableObject.Instantiate(bakeData);
            _RuntimeData.hideFlags = HideFlags.HideAndDontSave;
            
            SetUpCullingGroup(camera);
            
            VGInstancingDataRenererManager.GetInstance().Register(this);

            _isInited = true;
        }


        void DeInit()
        {
            
            VGInstancingDataRenererManager.GetInstance().UnRegister(this);
            
            CleanUpCullingGroup();

#if UNITY_EDITOR
            DestroyImmediate(_RuntimeData);
#else
            Destroy(_RuntimeData);
#endif

            _isInited = false;
        }


        public void Refresh()
        {
            DeInit();
            Init();
        }
        
        

        public void CommitInstances(VGBatchUtils batcher, IVGInstancingMaterialManager materialManager)
        {
            if (!_isInited)
                return;
            
            foreach (var block in _RuntimeData.blocks)
            {
                if (!block.isVisible || block.instancesCount <= 0)
                    continue;

                foreach (var instance in block.instances)
                {
                    var prefab = _RuntimeData.prefabs[instance.prefabIndex];
                    Material renderingMaterial = materialManager.GetLodMaterial(prefab.material, block.lodLevel);
                    batcher.CommitInstance(prefab.mesh, renderingMaterial, instance.worldMatrix);
                }
            }
        }

        void SetUpCullingGroup(Camera camera)
        {
            if (_CullingGroup != null)
                CleanUpCullingGroup();
                
            BoundingSphere[] bsArr = new BoundingSphere[_RuntimeData.blocksCount];
            for (int i = 0; i < _RuntimeData.blocksCount; i++)
            {
                Bounds b = _RuntimeData.blocks[i].bounds;
                Vector3 offset = new Vector3(0, _RuntimeData.blockHeightOffset + heightOffset, 0);
                bsArr[i] = new BoundingSphere(b.center + offset, Mathf.Min(Mathf.Min(b.extents.x, b.extents.y), b.extents.z));
            }
            
            float[] lodArr = new float[lodCount + 1];
            for (int j = 0; j < lodCount; j++)
            {
                lodArr[j] = lodDistances[j];
            }
            lodArr[lodCount] = lodDistances.w;

            

            _CullingGroup = new CullingGroup();
            _CullingGroup.SetBoundingSphereCount(_RuntimeData.blocksCount);
            _CullingGroup.SetBoundingSpheres(bsArr);
            _CullingGroup.targetCamera = camera;
            _CullingGroup.SetDistanceReferencePoint(camera.transform);
            
            _CullingGroup.SetBoundingDistances(lodArr);
            _CullingGroup.onStateChanged += OnCullingSphereStateChange;
        }

        void CleanUpCullingGroup()
        {
            if (_CullingGroup != null)
            {
                _CullingGroup.onStateChanged -= OnCullingSphereStateChange;
                _CullingGroup.Dispose();
                _CullingGroup = null;                
            }
        }

        void OnCullingSphereStateChange(CullingGroupEvent e)
        {
            VGBlockData block = _RuntimeData.blocks[e.index];
            block.lodLevel = e.currentDistance;
            block.isVisible = e.isVisible ? e.isVisible 
                            : GeometryUtility.TestPlanesAABB(GeometryUtility.CalculateFrustumPlanes(_CullingGroup.targetCamera), block.bounds);
            block.isVisible &= block.lodLevel < lodCount;
        }


        Camera GetCullingCamera()
        {
#if UNITY_EDITOR
            if (Application.isPlaying)
                return Camera.main;

            return SceneView.lastActiveSceneView ? SceneView.lastActiveSceneView.camera : null;

#endif
            return Camera.main;
        }

    }
}